package com.polidea.rxohosble2.internal.operations;

import com.polidea.rxohosble2.LogConstants;
import com.polidea.rxohosble2.internal.RxBleLog;
import com.polidea.rxohosble2.internal.logger.LoggerUtil;
import com.polidea.rxohosble2.internal.scan.OhosScanObjectsConverter;
import com.polidea.rxohosble2.internal.scan.EmulatedScanFilterMatcher;
import com.polidea.rxohosble2.internal.scan.InternalScanResultCreator;
import com.polidea.rxohosble2.internal.scan.RxBleInternalScanResult;
import com.polidea.rxohosble2.internal.util.RxBleAdapterWrapper;
import com.polidea.rxohosble2.scan.ScanFilter;
import com.polidea.rxohosble2.scan.ScanSettings;
import com.polidea.rxohosble2.exceptions.BleScanException;

import java.util.Arrays;
import java.util.List;

import io.reactivex.ObservableEmitter;
import com.polidea.rxohosble2.annotation.NonNull;
import com.polidea.rxohosble2.annotation.Nullable;
import ohos.bluetooth.ble.BleScanResult;

public class ScanOperationApi4 extends ScanOperation<RxBleInternalScanResult, RxBleAdapterWrapper.ScanCallback> {

    @NonNull
    final InternalScanResultCreator internalScanResultCreator;
    @NonNull
    private final OhosScanObjectsConverter ohosScanObjectsConverter;
    @NonNull
    private final ScanSettings scanSettings;
    @NonNull
    final EmulatedScanFilterMatcher emulatedScanFilterMatcher;
    @Nullable
    private final ScanFilter[] scanFilters;
    @Nullable
    private ObservableEmitter<RxBleInternalScanResult> scanEmitter;


    public ScanOperationApi4(
            @NonNull RxBleAdapterWrapper rxBleAdapterWrapper,
            @NonNull final InternalScanResultCreator internalScanResultCreator,
            @NonNull final OhosScanObjectsConverter ohosScanObjectsConverter,
            @NonNull ScanSettings scanSettings,
            @NonNull final EmulatedScanFilterMatcher emulatedScanFilterMatcher,
            @Nullable final ScanFilter[] offloadedScanFilters
    ) {
        super(rxBleAdapterWrapper);
        this.internalScanResultCreator = internalScanResultCreator;
        this.scanSettings = scanSettings;
        this.emulatedScanFilterMatcher = emulatedScanFilterMatcher;
        this.scanFilters = offloadedScanFilters;
        this.ohosScanObjectsConverter = ohosScanObjectsConverter;
        scanEmitter = null;
    }

    @Override
    RxBleAdapterWrapper.ScanCallback createScanCallback(final ObservableEmitter<RxBleInternalScanResult> emitter) {
        scanEmitter = emitter;
        return new RxBleAdapterWrapper.ScanCallback() {
            @Override
            public void scanResultEvent(BleScanResult result) {
                if (!emulatedScanFilterMatcher.isEmpty()
                        && RxBleLog.isAtLeast(LogConstants.DEBUG)
                        && RxBleLog.getShouldLogScannedPeripherals()) {
                    BleScanResult scanRecord = result;
                    RxBleLog.d("%s, name=%s, rssi=%d, data=%s",
                            LoggerUtil.commonMacMessage(result.getPeripheralDevice().getDeviceAddr()),
                            result.getPeripheralDevice().getDeviceName().orElse(""),
                            result.getRssi(),
                            LoggerUtil.bytesToHex(scanRecord != null ? scanRecord.getRawData() : null)
                    );
                }
                final RxBleInternalScanResult internalScanResult = internalScanResultCreator.create(result);
                if (emulatedScanFilterMatcher.matches(internalScanResult)) {
                    ObservableEmitter<RxBleInternalScanResult> refScanEmitter = scanEmitter;
                    if (refScanEmitter != null) {
                        refScanEmitter.onNext(internalScanResult);
                    }
                }
            }

            @Override
            public void groupScanResultsEvent(List<BleScanResult> list) {
                for (BleScanResult result : list) {
                    final RxBleInternalScanResult internalScanResult = internalScanResultCreator.create(result);
                    if (emulatedScanFilterMatcher.matches(internalScanResult)) {
                        ObservableEmitter<RxBleInternalScanResult> refScanEmitter = scanEmitter;
                        if (refScanEmitter != null) {
                            refScanEmitter.onNext(internalScanResult);
                        }
                    }
                }
            }

            @Override
            public void scanFailedEvent(int i) {
                ObservableEmitter<RxBleInternalScanResult> refScanEmitter = scanEmitter;
                if (refScanEmitter != null) {
                    refScanEmitter.tryOnError(new BleScanException(errorCodeToBleErrorCode(i)));
                }
            }
        };
    }

    @Override
    boolean startScan(RxBleAdapterWrapper rxBleAdapterWrapper, RxBleAdapterWrapper.ScanCallback scanCallback) {
        if (this.emulatedScanFilterMatcher.isEmpty()) {
            RxBleLog.d("No library side filtering —> debug logs of scanned devices disabled");
        }
        rxBleAdapterWrapper.startLeScan(
                ohosScanObjectsConverter.toNativeFilters(scanFilters),
                ohosScanObjectsConverter.toNativeSettings(scanSettings),
                scanCallback
        );
        return true;
    }

    @Override
    void stopScan(RxBleAdapterWrapper rxBleAdapterWrapper, RxBleAdapterWrapper.ScanCallback scanCallback) {
        rxBleAdapterWrapper.stopLeScan(scanCallback);
        if (scanEmitter != null) {
            scanEmitter.onComplete();
            scanEmitter = null;
        }
    }

    @BleScanException.Reason
    static int errorCodeToBleErrorCode(int errorCode) {
        switch (errorCode) {
//            case BleCentralManagerCallback.SCAN_FAILED_ALREADY_STARTED:
//                return BleScanException.SCAN_FAILED_ALREADY_STARTED;
//            case BleCentralManagerCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
//                return BleScanException.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
//            case BleCentralManagerCallback.SCAN_FAILED_FEATURE_UNSUPPORTED:
//                return BleScanException.SCAN_FAILED_FEATURE_UNSUPPORTED;
//            case BleCentralManagerCallback.SCAN_FAILED_INTERNAL_ERROR:
//                return BleScanException.SCAN_FAILED_INTERNAL_ERROR;
//            case 5: // BleCentralManagerCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES
//                return BleScanException.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES;
            default:
                RxBleLog.w("Encountered unknown scanning error code: %d -> check ohos.bluetooth.le.BleCentralManagerCallback");
                return BleScanException.UNKNOWN_ERROR_CODE;
        }
    }

    @Override
    public String toString() {
        boolean scanFiltersEmpty = scanFilters == null || scanFilters.length == 0;
        boolean emulatedScanFiltersEmpty = emulatedScanFilterMatcher.isEmpty();
        return "ScanOperationApi21{"
                + (scanFiltersEmpty ? "" : "ANY_MUST_MATCH -> nativeFilters=" + Arrays.toString(scanFilters))
                + (!scanFiltersEmpty && !emulatedScanFiltersEmpty ? " and then " : "")
                + (emulatedScanFiltersEmpty ? "" : "ANY_MUST_MATCH -> " + emulatedScanFilterMatcher)
                + '}';
    }
}
